<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
  
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  
  <link href="../css/style.css" rel="stylesheet" type="text/css">

</head>
<body>
<h1>4.16. Вывод чисел на консоль</h1>
<p class="article">
В качестве примера программирования процедур займёмся такой важной проблемой, как вывод на консоль чисел в различных системах счисления. Проблема эта возникает потому, что в ассемблере нет никаких специальных средств для вывода чисел, а с помощью стандартных функций можно выводить только строки.
Следовательно, задача сводится к тому, чтобы преобразовать двоичное число в строку символов, а затем вывести эту строку на экран. Все процедуры в этой части являются лишь примерами, вы можете использовать их или написать свои собственные процедуры, более удобные для вас.
</p>
<p class="article">
Для начала рассмотрим две полезные процедуры, которые будут использоваться в дальнейшем. Чтобы постоянно не обращаться в коде в функции DOS 09h, удобно написать маленькую процедуру для вывода строки:
</p>
<pre class="code">
;Процедура вывода строки на консоль
; DI - адрес строки
print_str:
    push ax
    mov ah,9                ;Функция DOS 09h - вывод строки
    xchg dx,di              ;Обмен значениями DX и DI
    int 21h                 ;Обращение к функции DOS
    xchg dx,di              ;Обмен значениями DX и DI
    pop ax
    ret
</pre>
<p class="article">
В качестве параметра ей передаётся адрес строки в регистре DI. Строка должна оканчиваться символом ‘$’. Здесь используется команда XCHG, которая выполняет обмен значениями двух операндов.
</p>
<p class="article">
Вторая полезная процедура — вывод конца строки. Она вызывает первую процедуру для вывода двух символов CR(13) и LF(10). Вызывается без параметров и не изменяет регистры.
</p>
<pre class="code">
;Процедура вывода конца строки (CR+LF)
print_endline:
    push di
    mov di,endline          ;DI = адрес строки с символами CR,LF
    call print_str          ;Вывод строки на консоль
    pop di
    ret
...
endline db 13,10,'$'
</pre>
<h2>Вывод чисел в двоичном виде</h2>
<p class="article">
Алгоритм вывода в двоичном виде очень прост. Нужно проанализировать все биты числа и поместить в строку символы ’0′ или ’1′ в зависимости от значения соответствующего бита. Удобно делать это с помощью циклического сдвига в цикле. Сдвинутый бит оказывается в флаге CF, а после завершения цикла в регистре то же значение, что и в начале. Процедура byte_to_bin_str преобразует байт в регистре AL в строку. Адрес буфера для строки передаётся в регистре DI. Процедура всегда записывает в буфер 8 символов, так как в байте 8 бит.
</p>
<pre class="code">
;Процедура преобразования байта в строку в двоичном виде
; AL - байт.
; DI - буфер для строки (8 символов). Значение регистра не сохраняется.
byte_to_bin_str:
    push cx                 ;Сохранение CX
    mov cx,8                ;Счётчик цикла
 
btbs_lp:
    rol al,1                ;Циклический сдвиг AL влево на 1 бит
    jc btbs_1               ;Если выдвинутый бит = 1, то переход
    mov byte[di],'0'        ;Добавление символа '0' в строку
    jmp btbs_end
btbs_1:
    mov byte[di],'1'        ;Добавление символа '1' в строку
btbs_end:
    inc di                  ;Инкремент DI
    loop btbs_lp            ;Команда цикла
 
    pop cx                  ;Восстановление CX
    ret                     ;Возврат из процедуры
</pre>
<p class="article">
Используя эту процедуру, легко написать ещё одну для вывода слова в двоичном виде:
</p>
<pre class="code">
;Процедура преобразования слова в строку в двоичном виде
; AX - слово
; DI - буфер для строки (16 символов). Значение регистра не сохраняется.
word_to_bin_str:
    xchg ah,al              ;Обмен AH и AL
    call byte_to_bin_str    ;Преобразование старшего байта в строку
    xchg ah,al              ;Обмен AH и AL
    call byte_to_bin_str    ;Преобразование младшего байта в строку
    ret
</pre>
<p class="article">
И наконец вот две процедуры, которые делают то, что нужно.  Буфер имеет размер 17 символов, так как в слове 16 бит + символ ‘$’, обозначающий конец строки.
</p>
<pre class="code">
;Процедура вывода байта на консоль в двоичном виде
; AL - байт
print_byte_bin:
    push di
    mov di,buffer           ;DI = адрес буфера
    call byte_to_bin_str    ;Преобразование байта в AL в строку
    mov byte[di],'$'        ;Добавление символа конца строки
    sub di,8                ;DI = адрес начала строки
    call print_str          ;Вывод строки на консоль
    pop di
    ret
 
;Процедура вывода слова на консоль в двоичном виде
; AX - слово
print_word_bin:
    push di
    mov di,buffer           ;DI = адрес буфера
    call word_to_bin_str    ;Преобразование слова в AX в строку
    mov byte[di],'$'        ;Добавление символа конца строки
    sub di,16               ;DI = адрес начала строки
    call print_str          ;Вывод строки на консоль
    pop di
    ret
...
buffer  rb 17
</pre>
<h2>Вывод чисел в шестнадцатеричном виде</h2>
<p class="article">
Этот пример по структуре похож на предыдущий, поэтому для краткости я рассмотрю только сами процедуры преобразования числа в строку. Преобразование в шестнадцатеричный вид удобно выполнять группами по 4 бита, то есть по тетрадам. Каждая тетрада будет представлять собой одну шестнадцатеричную цифру. Я написал отдельную процедуру для преобразования тетрады в символ цифры:
</p>
<pre class="code">
;Процедура преобразования числа (0-15) в шестнадцатеричную цифру
; вход : AL - число (0-15)
; выход: AL - шестнадцатеричная цифра ('0'-'F')
to_hex_digit:
    add al,'0'              ;Прибавляем символ '0' (код 0x30)
    cmp al,'9'              ;Сравнение с символом '9' (код 0x39)
    jle thd_end             ;Если получилось '0'-'9', то выход
    add al,7                ;Прибавляем ещё 7 для символов 'A'-'F'
thd_end:
    ret
</pre>
<p class="article">
Если значение тетрады от 0 до 9, то достаточно только прибавить код символа ’0′ (0×30). А если значение больше 9, то надо прибавить ещё 7, чтобы получилась буква ‘A’-'F’.
Теперь легко можно преобразовать байт в шестнадцатеричную строку, достаточно каждую из его тетрад заменить соответствующей цифрой:
</p>
<pre class="code">
;Процедура преобразования байта в строку в шестнадцатеричном виде
; AL - байт.
; DI - буфер для строки (2 символа). Значение регистра не сохраняется.
byte_to_hex_str:
    push ax
    mov ah,al               ;Сохранение значения AL в AH
    shr al,4                ;Выделение старшей тетрады
    call to_hex_digit       ;Преобразование в шестнадцатеричную цифру
    mov [di],al             ;Добавление символа в строку
    inc di                  ;Инкремент DI
    mov al,ah               ;Восстановление AL
    and al,0Fh              ;Выделение младшей тетрады
    call to_hex_digit       ;Преобразование в шестнадцатеричную цифру
    mov [di],al             ;Добавление символа в строку
    inc di                  ;Инкремент DI
    pop ax
    ret
</pre>
<p class="article">
Преобразование слова также не представляет трудности — сначала преобразуем старший байт, затем младший:
</p>
<pre class="code">
;Процедура преобразования слова в строку в шестнадцатеричном виде
; AX - слово
; DI - буфер для строки (4 символа). Значение регистра не сохраняется.
word_to_hex_str:
    xchg ah,al              ;Обмен AH и AL
    call byte_to_hex_str    ;Преобразование старшего байта в строку
    xchg ah,al              ;Обмен AH и AL
    call byte_to_hex_str    ;Преобразование младшего байта в строку
    ret
</pre>
<h2>Вывод чисел в десятичном виде</h2>
<p class="article">
С десятичными числами немного сложнее. Для начала займёмся числами без знака. Чтобы преобразовать число в десятичную строку необходимо в цикле делить его на 10 (это основание системы счисления). Остатки от деления дают нам значения десятичных цифр. Первый остаток — младшая цифра, последний — старшая. Деление продолжается пока частное не равно нулю.
</p>
<p class="article">
Например, если есть число 125. Делим его на десять: получаем 12, 5 в остатке. Потом делим 12 на десять: получаем 1, 2 в остатке. Наконец, 1 делим на 10: получаем 0, 1 в остатке. Цифры числа, начиная с младшей: 5, 2, 1. Так как обычно десятичные числа пишут, начиная со старшей цифры, то необходимо переставить их наоборот. Для этих целей подойдет стек.
</p>
<p class="article">
В первом цикле производится деление, полученные остатки преобразуются в цифры и помещаются в стек. Во втором цикле символы извлекаются из стека (в обратном порядке) и помещаются в строку. Так как максимальное значение слова без знака 65536 (5 цифр), то в буфер записывается максимум 5 символов.
</p>
<pre class="code">
;Процедура преобразования слова в строку в десятичном виде (без знака)
; AX - слово
; DI - буфер для строки (5 символов). Значение регистра не сохраняется.
word_to_udec_str:
    push ax
    push cx
    push dx
    push bx
    xor cx,cx               ;Обнуление CX
    mov bx,10               ;В BX делитель (10 для десятичной системы)
 
wtuds_lp1:                  ;Цикл получения остатков от деления
    xor dx,dx               ;Обнуление старшей части двойного слова
    div bx                  ;Деление AX=(DX:AX)/BX, остаток в DX
    add dl,'0'              ;Преобразование остатка в код символа
    push dx                 ;Сохранение в стеке
    inc cx                  ;Увеличение счетчика символов
    test ax,ax              ;Проверка AX
    jnz wtuds_lp1           ;Переход к началу цикла, если частное не 0.
 
wtuds_lp2:                  ;Цикл извлечения символов из стека
    pop dx                  ;Восстановление символа из стека
    mov [di],dl             ;Сохранение символа в буфере
    inc di                  ;Инкремент адреса буфера
    loop wtuds_lp2          ;Команда цикла
 
    pop bx
    pop dx
    pop cx
    pop ax
    ret
</pre>
<p class="article">
Для вывода байта можно преобразовать его в слово и воспользоваться той же процедурой:
</p>
<pre class="code">
;Процедура преобразования байта в строку в десятичном виде (без знака)
; AL - байт.
; DI - буфер для строки (3 символа). Значение регистра не сохраняется.
byte_to_udec_str:
    push ax
    xor ah,ah               ;Преобразование байта в слово (без знака)
    call word_to_udec_str   ;Вызов процедуры для слова без знака
    pop ax
    ret
</pre>
<p class="article">
Теперь разберёмся с числами со знаком. Сначала нужно проверить старший бит числа. Если число положительное, то его можно преобразовать также как число без знака. Если число отрицательное, то добавляем в строку символ ‘-’, а затем инвертируем число и преобразуем как беззнаковое.
</p>
<pre class="code">
;Процедура преобразования слова в строку в десятичном виде (со знаком)
; AX - слово
; DI - буфер для строки (6 символов). Значение регистра не сохраняется.
word_to_sdec_str:
    push ax
    test ax,ax              ;Проверка знака AX
    jns wtsds_no_sign       ;Если >= 0, преобразуем как беззнаковое
    mov byte[di],'-'        ;Добавление знака в начало строки
    inc di                  ;Инкремент DI
    neg ax                  ;Изменение знака значения AX
wtsds_no_sign:
    call word_to_udec_str   ;Преобразование беззнакового значения
    pop ax
    ret
</pre>
<pre class="code">
;Процедура преобразования байта в строку в десятичном виде (со знаком)
; AL - байт.
; DI - буфер для строки (4 символа). Значение регистра не сохраняется.
byte_to_sdec_str:
    push ax
    movsx ax,al             ;Преобразование байта в слово (со знаком)
    call word_to_sdec_str   ;Вызов процедуры для слова со знаком
    pop ax
    ret
</pre>
</body>
</html>
